
import re
import json
import random
from openai import OpenAI
from loguru import logger

logger.remove()  # 移除默认的handler
logger.add(
    sink="file.log",
    format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[traceId]} | {thread.name} | {name} | {message}",
    level="DEBUG",  # 显式指定日志级别
    backtrace=True,
    diagnose=True,
)
client = OpenAI(
    api_key="",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

def predbailian(prompt):
    """
    调大模型
    :param prompt:
    :return:
    """
    for i in range(10):
        try:
            completion = client.chat.completions.create(
                model="qwen-plus",
                messages=[
                    {'role': 'system', 'content': 'You are a helpful assistant.'},
                    {'role': 'user', 'content': prompt}],
                temperature=0.001
            )

            content = completion.choices[0].message.content
            return content
        except:
            print(11111)





# 清洗得分标签
def cleaned_score_labels(id_sort_checkpoint, score_label,standard_answer):
    """
    根据命中的检核点，检索应该回复的标准话术
    :param id_sort_checkpoint: 当前命中的检核点排序后的list ['询问客户姓氏']
    :param score_label: 已得分的检核点列表 []
    :param standard_answer: 检核点对应的标准回答
    {'询问客户姓氏': {'index': 'C1', 'intent': '无意图', 'standard_script': '您好，欢迎光临，女士您贵姓？', 'robot_response': '姓赵'},
    '询问客户进店意图': {'index': 'C2', 'intent': '无意图', 'standard_script': '赵女士您好，您是看车还是维修保养？', 'robot_response': '看车'}}
    :return:
    """

    logger.info(f"1 id_sort_checkpoint: {id_sort_checkpoint}")
    logger.info(f"1 score_label: {score_label}")
    logger.info(f"1 standard_answer: {standard_answer}")

    # 找到当前命中的，但未得分的检核点
    labels = [i for i in id_sort_checkpoint if i not in score_label]

    logger.info(f"1 labels: {labels}")

    if len(labels) == 0:
        res_label = id_sort_checkpoint
    else:
        # 筛选并获取最后一个有"有意图"的条目
        intentional_items = [label for label in labels if standard_answer.get(label, {}).get('intent') == '有意图']
        # 无意图条目：
        non_intentional_items = [label for label in labels if standard_answer.get(label, {}).get('intent') == '无意图']
        # 获取最后一个元素
        last_intentional_item = [intentional_items[0]] if intentional_items else None
        if last_intentional_item is not None:
            res_label= last_intentional_item + non_intentional_items
        else:
            res_label = non_intentional_items
    return res_label
            

from collections import deque
def find_all_matching_sublists(target_list, input_elements):
    # 将目标列表转换为集合以便快速查找
    target_set = set(target_list)

    # 用于存储找到的匹配子列表
    matching_sublists = []

    # 遍历输入元素中的每个子列表
    for sublist in input_elements:
        # 如果子列表的所有元素都在目标列表中，并且没有额外的元素，则认为是匹配的
        if set(sublist).issubset(target_set) and len(sublist) == len(set(sublist).intersection(target_set)):
            matching_sublists.append(sublist) 
    return matching_sublists

def merge_consecutive_sublists(matching_sublists):
    # 用于存储最终结果的列表
    merged_sublists = []
    # 使用deque来高效地从两端添加或移除元素
    to_process = deque(matching_sublists)

    while to_process:
        current_sublist = to_process.popleft()
        merged = False

        # 尝试将当前子列表与已经合并的子列表合并
        for i, merged_list in enumerate(merged_sublists):
            if set(current_sublist).intersection(set(merged_list)):
                # 如果有共同元素，则合并
                # 注意：这可能会导致重复元素，如果不需要重复元素，需要额外处理
                new_merged_list = merged_list + [item for item in current_sublist if item not in merged_list]
                merged_sublists[i] = new_merged_list
                merged = True
                break

        # 如果没有合并到任何已有的子列表中，则作为一个新的合并列表加入
        if not merged:
            merged_sublists.append(current_sublist)

        # 再次检查是否有可以进一步合并的子列表
        for _ in range(len(to_process)):
            next_sublist = to_process.popleft()
            for i, merged_list in enumerate(merged_sublists):
                if set(next_sublist).intersection(set(merged_list)):
                    # 合并并且重新放入队尾以检查更多合并可能性
                    new_merged_list = merged_list + [item for item in next_sublist if item not in merged_list]
                    merged_sublists[i] = new_merged_list
                    break
            else:
                # 如果不能合并任何现有的子列表，则放回队列末尾
                to_process.append(next_sublist)

    return merged_sublists


# 返回排序后应该返回的检核点
def index_sort(topic_cls, intent_res, index_to_label):
    # 需要返回的检核点
    id_sort_checkpoint = list()
    topic_points = topic_cls['topic_point']
    topic_groups = topic_cls['topic_group']
    # 替换 topic_point 中的索引为标签
    topic_point = [index_to_label[index] for index in topic_points]

    # 替换 topic_group 中的索引为标签
    topic_group = [[index_to_label[index] for index in group] for group in topic_groups]

    for point in intent_res:
        if point in topic_point:
            id_sort_checkpoint.append(point)
        
        max_index = -1
        target_group = None
        
        # 寻找包含point的子列表，并记录其在topic_group中的最大索引
        for index, groups in enumerate(topic_group):
            if point in groups and index > max_index:
                max_index = index
                target_group = groups
        
        if target_group is not None:
            idx = target_group.index(point)
            if idx < len(target_group) - 1:  # 检查point是否不是最后一个元素
                id_sort_checkpoint.append(target_group[idx])
            else:
                id_sort_checkpoint.append(target_group[-1])  # 如果point是最后一个元素，则添加最后一个元素
    id_sort_checkpoint = list(set(id_sort_checkpoint))
    # id_sort_checkpoint = find_all_matching_sublists(id_sort_checkpoint, topic_group)
    matching_sub =  find_all_matching_sublists(id_sort_checkpoint,topic_group) # 输出: ['送贴膜', '进店意图']

    res = merge_consecutive_sublists(matching_sub)
    target_list_two = [j for i in res for j in i]

    for i in target_list_two:
        id_sort_checkpoint.pop(id_sort_checkpoint.index(i))
    res2 = [i[-1] for i in  res]
    result = res2 + id_sort_checkpoint

    print("需要回复的标签："+str(result))
    return result

# 得到意图和正则的数据
def anylse_checkpoint(chushi_checkpoint):
    intent_list = list()
    regular_list = list()
    for i in chushi_checkpoint:
        if i['check_regular'] == '意图':
            intent_list.append(i['checkpoint'])
        else:
            regular_list.append([i['checkpoint'], i['regex']])

    return intent_list, regular_list

# 对于用户的闲聊语句，给与对应的输出
def chatbot_bailian(inputs):
    prompt = f"""
    角色定义：购车的客户，需要对销售的话进行谨慎的回复。
    职业：客户

    要求：
    如果输入的问题包含脏话或者违禁词汇，你要给予严重的警告。
    除了回复之外，其余不需要输出，不要输出json格式。
    你要仿照正例客户的回复，不要学习反例，不要反问销售。
    禁止追问销售，说的话不要有意图。
    请用中文输出结果。


    注意：
    首先先检查自己的回复，只有符合要求再输出。
    其次禁止追问
    然后禁止重复对话
    最后你自己的身份定位是客户。
    你的回复中不能带有人名、姓名等称呼。
    销售的话可能与车无关，你也需要回复，禁止追问。



    正例1：
    销售：您好，请问您贵姓。
    客户：我姓李。

    正例2：
    销售：天气真好
    客户：是的

    正例3：
    销售：led高性能大灯真不错。
    客户：确实

    正例4：
    销售：河南菜好吃。
    销售：很好吃。

    反例1：
    销售：您好，请问您贵姓。
    客户：我姓赵，请问你要做什么？

    反例2：
    销售：明天吃什么
    客户：明天吃什么就看个人喜好了，我个人比较喜欢简单的吃点清淡的。回到车上的话题，您能给我推荐几款省油又实用的车型吗？

    销售的话：
    {inputs}
    """
    chat_talk = predbailian(prompt)
    return chat_talk


# 获得语句中的标签
def get_labels(user_input, chushi_checkpoint, intent_list, regular_list):
    describe = list()
    # 更改项
    regular_res = list()
    for re1 in regular_list:
        if re.search(re1[1], user_input):
            regular_res.append(re1[0])
    logger.info("正则检核点匹配结果：" + '--'.join(regular_res))
    # 拼接描述
    for i in chushi_checkpoint:
        for j in intent_list:
            if i['checkpoint'] == j:
                describe.append(j + ':' + i["checkpoint_describe"])
    test_describe = '\n'.join(describe)
    # print(test_describe)
    label_list = '\n'.join(intent_list)
    if len(intent_list) != 0:
        prompt = f"""
            你是一个标签机器人，你的作用是将用户说的话匹配到对应的标签。注意:
            一句话可能不止一个标签,也可能没有标签,
            只需要输出对应标签名，其余不需要输出。
            如果有多个标签请用--来分割。
            如果没有标签，请输出无标签。
            标签定义:
            {test_describe}
            标签:
            {label_list}
            用户的话:
            {user_input}
                """
        intent_res = predbailian(prompt)
        logger.info("模型检核点匹配结果：" + intent_res)
    else:
        intent_res = '无标签'
        logger.info("模型检核点匹配结果：" + intent_res)

    if '无标签' not in intent_res:
        res_list = intent_res.split('--') + regular_res
        return res_list

    if '无标签' in intent_res:
        return regular_res

 # 将标签对应的标准话术转化为markdown格式
def generate_table(dialogues, checkpoints):
    # 初始化表格标题
    table = "| 销售问询 | 客户回复 |\n| --- | --- |\n"
    if isinstance(checkpoints, str):
        checkpoints = [checkpoints]
    # 遍历检核点列表
    for checkpoint in checkpoints:
        # 从对话数据中获取对应的销售问询和客户回复
        if checkpoint in dialogues:
            sales_question = dialogues[checkpoint]['standard_script']
            customer_response = dialogues[checkpoint]['robot_response']
            # 将对话添加到表格中
            table += f"| {sales_question} | {customer_response} |\n"
        else:
            # 如果检核点不在对话数据中，可以添加一个提示或者忽略
            table += f"| {checkpoint} (未找到对话) | - |\n"
    logger.info("拼接的markdown话术："+'\n'+str(table))
    return table

    # 获得大模型对应的输出
def get_chat_output(generated_speech,mark_ouput_label, res_label):
    # 拿取缓存话术
    if len(res_label) == 1 and len(generated_speech) != 0:
        lbs = res_label[0]
        for i in generated_speech:
            if lbs == i['checkpoint']:
                if len(i['simulatedCustomerSpeech']) == 0:
                    gene_res = random.choice(i['defaultRoleSpeech'])
                else:
                    gene_res = random.choice(i['simulatedCustomerSpeech'])

                return gene_res


    chat_prompt = f"""
                你的身份：
                    你是一个模拟对话机器人，模拟对象是客户，跟你对话的是销售。

                背景介绍：
                    “销售问询”是销售询问你的问题，“客户回复”是你要参照的回复结果，实际问题场景中销售是将“销售问询”中的合并在一起询问了，因此你要将“客户回复”中的话口语化的归纳成一句话。

                要求：
                    你的角色是客户，请不要代入销售角色。
                    如果这“客户回复”中有提问含义的话请保留提问
                    你的输出内容要符合“你的性格特点”同时也要“简短口语化”。
                    你只需要输出对话内容，不要输出任何无关句子。

                注意：
                    请不要混淆自己的角色、姓名
                    请回答的随意一点，像真人一样说话，而不是在打字回复别人，尽量不要用敬词
                    请再次检查你的回复是否能精简点 合并成简短且通顺的一句话 但不要丢失其中一个回复的信息
                    你只需要输出“客户回复”中的内容，严禁输出“销售问询”内容。
                    如果“客户回复”有多条，你要合并一起输出内容。

                简短口语化聊天案例：
                    标准说话：今天天气真好，万里无云，好想去公园踏青游玩啊，
                    简短口语化：天不赖，想去公园玩

                {mark_ouput_label}
                """
    # res = glm_predict(chat_prompt)
    res = predbailian(chat_prompt)
    return res

# 拿到每一个参数
def get_params(data, traceId='1'):
    with logger.contextualize(traceId=traceId):
        logger.info("入参："+str(data))
        input = data['inputs'][0]['robotTalk'][0]
        logger.info("输入聊天："+input)
        # 状态判定
        status_judge = data['status_judge']
        # 缓存话术
        generated_speech = data['generated_speech']
        # his_label 
        his_label = data['his_label']
        # 闲聊次数
        allow_chat_count = data['allow_chat_count']
        # 意图和规则
        chushi_checkpoint = data['chushi_checkpoint']
        # 剩余标签
        remaining_tags = data['remaining_tags']
        # 得分标签
        current_label = data['current_label']
        # 话题点和话题对
        topic_cls = data['topic_cls']
        # 标准话术
        standard_answer = data['standard_answers']
        # 标签对应id 
        index_to_label = data['index_to_label']
        # 区分意图分类和规则分类
        intent_list, regular_list = anylse_checkpoint(chushi_checkpoint)
        # 得到输入的分类结果
        intent_res = get_labels(input, chushi_checkpoint, intent_list, regular_list)
        

        # 判断是否是闲聊
        if len(intent_res)==0:
            status_judge = 1
            if allow_chat_count > 0:
                res = chatbot_bailian(input)
                res = res.replace('\n','')
                allow_chat_count = allow_chat_count -1
            else:
                res = "闲聊次数已用尽，本次聊天结束。"
            
        else:
            # 判断应该回答哪些检核点，基于id排序
            status_judge = 0
            id_sort_checkpoint = index_sort(topic_cls, intent_res, index_to_label)
            res_label = cleaned_score_labels(id_sort_checkpoint, his_label,standard_answer)
            current_label = res_label
            his_label = list(set(his_label + res_label))
            # pop掉得分标签
            for i in his_label:
                if i in remaining_tags:
                    remaining_tags.pop(remaining_tags.index(i))
            # 与得分标签做对比，已经得分的标签不做返回
            mark_label = generate_table(standard_answer, res_label)
            res = get_chat_output(generated_speech,mark_label, res_label)
            res = res.replace('\n','')
        logger.info("输出结果："+res)
        data['inputs'][0]['robotTalk'][0] = res
        data['remaining_tags'] = remaining_tags
        data['allow_chat_count'] = allow_chat_count
        data['current_label'] = current_label
        data['status_judge'] = status_judge
        data['his_label'] = his_label
        logger.info("最终返参："+str(data))
        return data


if __name__ == '__main__':
    while True:
        data = json.loads(open(r'a1.json','r',encoding='utf-8-sig').read())
        # anonymize['his_label'] = ['询问客户进店意图']
        inputs = input('请输入你的聊天：')
#         inputs = """
# 您好，欢迎光临，女士您贵姓？
# 赵女士您好，您是看车还是维修保养？

# """
        data['inputs'][0]['robotTalk'][0] = inputs
        data = get_params(data)
        print(data['inputs'][0]['robotTalk'][0])
        print("历史标签：",data['his_label'])
        print("当前标签：",data['current_label'])